﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Xml.Linq;

namespace CI.Models.Data
{
    public class RigidXmlObjectParser : IObjectParser
    {
        private static readonly string NamespacePrefix = typeof (XmlObjectParser).Namespace + ".";
        private readonly XDocument _document;
        private readonly List<Type> _listTypes = new List<Type>();
        private readonly Dictionary<string, Type> _typeCache = new Dictionary<string, Type>();

        public RigidXmlObjectParser(XDocument document)
        {
            if (document == null) throw new ArgumentNullException("document");

            _document = document;
        }

        #region IObjectParser Members

        public IEnumerable<T> GetObjects<T>()
        {
            var objs = new List<T>();

            foreach (XElement element in _document.Root.Elements())
            {
                objs.Add((T) ConstructInstanceFromElement(element));
            }

            return objs;
        }

        #endregion

        public void AddListType(Type listType)
        {
            if (!_listTypes.Contains(listType))
                _listTypes.Add(listType);
        }

        public Type GetType(string localName)
        {
            string key = NamespacePrefix + localName;

            if (!_typeCache.ContainsKey(key))
            {
                _typeCache.Add(key, Assembly.GetExecutingAssembly().GetType(key));
            }
            return _typeCache[key];
        }

        public object ConstructInstanceFromElement(XElement element)
        {
            object instance = Activator.CreateInstance(GetType(element.Name.LocalName));

            PopulatePropertiesFromAttributes(instance, element);
            PopulateChildrenFromSubElements(instance, element);

            return instance;
        }

        public IList<T> BuildListHelper<T>(T item)
        {
            var list = new List<T>();
            list.Add(item);
            return list;
        }

        private void PopulateChildrenFromSubElements(object instance, XElement element)
        {
            foreach (XElement subElement in element.Elements())
            {
                object childInstance = ConstructInstanceFromElement(subElement);

                string name = subElement.Name.LocalName;

                PropertyInfo property = instance.GetType().GetProperty(name);
                    
                if (_listTypes.Contains(childInstance.GetType()))
                {
                    property = instance.GetType().GetProperty(name + "s");
                    IList list = (IList) property.GetValue(instance, null);

                    list.Add(childInstance);
                }
                else
                {
                    property.SetValue(instance,childInstance,null);
                }
            }
        }

        private string UpperCaseAttribute(string attribute)
        {
            if (char.IsLower(attribute[0]))
                return char.ToUpper(attribute[0]) + attribute.Substring(1);
            else return attribute;
        }

        private void PopulatePropertiesFromAttributes(object instance, XElement element)
        {
            foreach (XAttribute attribute in element.Attributes())
            {
                string v = attribute.Value;
                var property = instance.GetType().GetProperty(UpperCaseAttribute(attribute.Name.LocalName));
                object o = Parse(v,property.PropertyType);

                

                property.SetValue(instance,o,null);
            }
        }

        public object Parse(string v, Type c)
        {
            if (typeof(int)==c)
            {
                return int.Parse(v);
            }
            else if (typeof(decimal)==c)
            {
                return decimal.Parse(v);
            }
            else if (typeof(bool)==c)
            {
                return bool.Parse(v);
            }
            else return v;
        }
    }
}